package com.kstudio.videocapture.editimage;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.view.MotionEvent;

import com.kstudio.videocapture.gl.ImageGL2D;
import com.kstudio.videocapture.gl.SquareClipGL2D;
import com.kstudio.videocapture.util.KLog;
import com.kstudio.videocapture.util.KUtil;

public class KImageEditViewGL extends GLSurfaceView implements Renderer {
    private final KLog log = new KLog("KImageEdit");

    private static final int INVALIDATE_NONE        = 0x0000;
    private static final int INVALIDATE_DO          = 0x0001;
    private static final int INVALIDATE_DOING       = 0x0002;
    private static final int INVALIDATE_INIT        = 0x0004;
    private static final int INVALIDATE_INIT_DONE   = 0x0008;

    private static final int ACTION_MODE_NONE = 0x0000;
    private static final int ACTION_MODE_DRAG = 0x0001;
    private static final int ACTION_MODE_ZOOM = 0x0002;
    private static final int ACTION_MODE_LINE_BOX_MOVE   = 0x0003;

    private ImageGL2D   mMainImage;
    private Bitmap      mMainBitmap;
    private String      mPath;

    private SquareClipGL2D mClipGl;

    private int mWidth;
    private int mHeight;

    private float mScale;
    private float mMinScale;
    private float mMaxScale;

    private int mInvalidate;
    private int mInvalidateCnt;

    private int     mActionMode             = ACTION_MODE_NONE;
    private PointF  mActionTransStartPos    = new PointF();
    private PointF  mActionZoomMidPos       = new PointF();
    private float   mActionZoomStartDist;
    private float   mActionZoomSavedPosX;
    private float   mActionZoomSavedPosY;
    private float   mActionZoomSavedScale;

    private int   mActionLineSavedLeft;
    private int   mActionLineSavedRight;
    private int   mActionLineSavedBottom;
    private int   mActionLineSavedTop;
    private int   mActionLineMode;

    public KImageEditViewGL(Context context) {
        this(context, null);
    }

    public KImageEditViewGL(Context context, AttributeSet attrs) {
        super(context, attrs);
        log.i("KImageEditViewWithGL");

        mInvalidate = INVALIDATE_NONE;
        mPath = null;

        this.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
        this.setRenderer(this);
        this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
        this.setZOrderMediaOverlay(true);

        setAttribute(context, attrs, 0);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        log.i("KImageEditViewWithGL : onSurfaceCreated");

        gl.glDisable(GL10.GL_DITHER);

        gl.glClearColor(0,0,0,0);

        gl.glEnable(GL10.GL_TEXTURE_2D);
        gl.glClearDepthf(1.0f);
        gl.glEnable(GL10.GL_DEPTH_TEST);
        gl.glDepthFunc(GL10.GL_LEQUAL);

        gl.glEnable(GL10.GL_BLEND);
//      gl.glBlendFunc(GL10.GL_ONE, GL10.GL_ONE);
        gl.glBlendFunc(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);
        gl.glEnable(GL10.GL_ALPHA_TEST);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        log.i("KImageEditViewWithGL : onSurfaceChanged");

        mWidth = width;
        mHeight = height;

        gl.glViewport(0, 0, width,height);

        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();

//      gl.glOrthof(0f, 1f, 0f, 1f, -1f, 1f);
        gl.glOrthof(0, width, 0, height, -1, 1);

    }

    @Override
    public void onDrawFrame(GL10 gl) {
        if((mInvalidate & INVALIDATE_INIT) == INVALIDATE_INIT) {
            init(gl);
            mInvalidate ^= INVALIDATE_INIT;
            mInvalidate |= INVALIDATE_DO | INVALIDATE_INIT_DONE;
        }

        if((mInvalidate & (INVALIDATE_INIT_DONE | INVALIDATE_DO)) == (INVALIDATE_INIT_DONE | INVALIDATE_DO) ||
           (mInvalidate & (INVALIDATE_INIT_DONE | INVALIDATE_DOING)) == (INVALIDATE_INIT_DONE | INVALIDATE_DOING)) {
            draw(gl);

            if((mInvalidate & INVALIDATE_DO) == INVALIDATE_DO) {
                mInvalidate ^= INVALIDATE_DO;
                mInvalidate |= INVALIDATE_DOING;
                mInvalidateCnt = 1;
            } else if((mInvalidate & INVALIDATE_DOING) == INVALIDATE_DOING) {
                if(mInvalidateCnt < 5)
                    mInvalidateCnt++;
                else
                    mInvalidate ^= INVALIDATE_DOING;
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction() & MotionEvent.ACTION_MASK;

        if(action == MotionEvent.ACTION_DOWN) {
            mActionTransStartPos.set(event.getX(), event.getY());

            if((mActionLineMode = mClipGl.checkInsideLineFromView(event.getX(), event.getY())) != 0) {
                mActionLineSavedLeft    = mClipGl.getLeftLn();
                mActionLineSavedRight   = mClipGl.getRightLn();
                mActionLineSavedBottom  = mClipGl.getBottomLn();
                mActionLineSavedTop     = mClipGl.getTopLn();

                mClipGl.setDrawLineFlag(mActionLineMode);
                mInvalidate |= INVALIDATE_DO;

                mActionMode = ACTION_MODE_LINE_BOX_MOVE;
            } else {
                savedActionData();
                mActionMode = ACTION_MODE_DRAG;
            }
            return true;
        } else {
            switch(mActionMode) {
                case ACTION_MODE_DRAG :
                    return action_Drag(event);
                case ACTION_MODE_ZOOM :
                    return action_Zoom(event);
                case ACTION_MODE_LINE_BOX_MOVE :
                    return action_LineBox_Move(event);
            }
        }
        return super.onTouchEvent(event);
    }

    public static int computeLinearListStartPos(int index, int totalSize, int listSize) {
        int computeStartPos = 0;

        if(totalSize < listSize || index < listSize / 2) {
            computeStartPos = 0;
        } else if(index > totalSize - listSize / 2) {
            computeStartPos = totalSize - listSize;
        } else {
            computeStartPos = index - listSize / 2;
        }

        return computeStartPos;
    }

    public void setInit(String path) {
        mPath = path;
        mMainBitmap = KUtil.loadBitmap(mPath);

        mInvalidate = INVALIDATE_INIT;
    }

    private void setAttribute(Context c, AttributeSet attrs, int defStyle) {
//        int[] ids = new int[attrs.getAttributeCount()];
//        for (int i = 0; i < attrs.getAttributeCount(); i++) {
//            ids[i] = attrs.getAttributeNameResource(i);
//        }
//
//        TypedArray a = c.obtainStyledAttributes(attrs, ids, defStyle, 0);
//
//        for (int i = 0; i < attrs.getAttributeCount(); i++) {
//            String attrName = attrs.getAttributeName(i);
//            if (attrName == null)
//                continue;
//
//        }
//
//        a.recycle();
    }

    private void init(GL10 gl) {
        log.i("KImageEditViewWithGL : init");

        int l = 0;
        int t = 0;
        int r = 0;
        int b = 0;

        if(mMainImage == null) {
            mMainImage = ImageGL2D.createFromBmp(gl, mMainBitmap);
            int imageWidth = mMainImage.getWidth();
            int imageHeight = mMainImage.getHeight();

            l = 0;
            r = l + imageWidth;
            b = 0;
            t = b + imageHeight;
            mMainImage.setLayout(l, r, b, t);

            mClipGl = SquareClipGL2D.create(gl);
            mClipGl.setViewHeight(mHeight);
            mClipGl.setLayout(l, r, b, t,
                             (int)(l + imageWidth * 0.25), (int)(l + imageWidth * 0.75), (int)(b + imageHeight * 0.25), (int)(b  + imageHeight * 0.75));

            mMinScale = 1f;

            if(imageWidth > mWidth ||
              imageHeight > mHeight ||
              (imageWidth < mWidth && imageHeight < mHeight)) {
                float scaleWidth = (float)((double) mWidth / (double) imageWidth);
                float scaleHeight = (float)((double) mHeight / (double) imageHeight);

                if(scaleWidth > scaleHeight) {
                    mMinScale = scaleHeight;
                } else {
                    mMinScale = scaleWidth;
                }
            }

            if(mMinScale < 1)  mMaxScale = 4f;
            else               mMaxScale = mMinScale * 4f;

            mScale = mMinScale;
            mMainImage.scale(mMinScale, mMinScale);
            mClipGl.scale(mMinScale, mMinScale);
            move(0f, 0f);
        }
    }

    private synchronized void draw(GL10 gl) {
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

        mMainImage.draw(gl);

        mClipGl.draw(gl);

        gl.glEnable(GL10.GL_BLEND);
//      gl.glBlendFunc(GL10.GL_ONE, GL10.GL_ONE);
        gl.glBlendFunc(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);
        gl.glEnable(GL10.GL_ALPHA_TEST);
    }

    private boolean action_Drag(MotionEvent event) {
        int action = event.getAction() & MotionEvent.ACTION_MASK;

        switch(action) {
          case MotionEvent.ACTION_POINTER_DOWN:
              mActionZoomStartDist = spacing(event);
              if(mActionZoomStartDist > 10f) {
                  savedActionData();
                  mActionZoomMidPos.set((event.getX(0) + event.getX(1)) / 2, (event.getY(0) + event.getY(1)) / 2);
                  mActionMode = ACTION_MODE_ZOOM;
              }
              return true;
          case MotionEvent.ACTION_MOVE:
              float moveX = mActionZoomSavedPosX + (event.getX() - mActionTransStartPos.x) / mMainImage.getScaleX();
              float moveY = mActionZoomSavedPosY + (mActionTransStartPos.y - event.getY()) / mMainImage.getScaleY();
              move(moveX , moveY);
              return true;
          case MotionEvent.ACTION_UP:
          case MotionEvent.ACTION_POINTER_UP:
              mActionMode = ACTION_MODE_NONE;
              return true;
        }
        return false;
    }

    private boolean action_Zoom(MotionEvent event) {
        int action = event.getAction() & MotionEvent.ACTION_MASK;

        switch(action) {
            case MotionEvent.ACTION_MOVE:
                float endDist = spacing(event);
                if(endDist > 10f) {
                    float scale = endDist / mActionZoomStartDist;
                    zoom(scale);
                }
                return true;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                mActionMode = ACTION_MODE_NONE;
                return true;
        }
        return false;
    }

    private synchronized boolean action_LineBox_Move(MotionEvent event) {
        int action = event.getAction() & MotionEvent.ACTION_MASK;

        switch(action) {
          case MotionEvent.ACTION_POINTER_DOWN:
              mActionZoomStartDist = spacing(event);
              if(mActionZoomStartDist > 10f) {
                  savedActionData();
                  mActionZoomMidPos.set((event.getX(0) + event.getX(1)) / 2, (event.getY(0) + event.getY(1)) / 2);
                  mClipGl.setDrawLineFlag(SquareClipGL2D.CLIP_LINE_CLICK_NONE);
                  mActionMode = ACTION_MODE_ZOOM;
              }
              return true;
          case MotionEvent.ACTION_MOVE:
              float moveX = event.getX() - mActionTransStartPos.x;
              float moveY = event.getY() - mActionTransStartPos.y;
              int glX = (int) mClipGl.transformMoveXCoordinateFromView(moveX);
              int glY = (int) mClipGl.transformMoveYCoordinateFromView(moveY);

              int l = mActionLineSavedLeft;
              int r = mActionLineSavedRight;
              int b = mActionLineSavedBottom;
              int t = mActionLineSavedTop;

              if((mActionLineMode & SquareClipGL2D.CLIP_LINE_CLICK_LEFT) == SquareClipGL2D.CLIP_LINE_CLICK_LEFT) l += glX;
              if((mActionLineMode & SquareClipGL2D.CLIP_LINE_CLICK_RIGHT) == SquareClipGL2D.CLIP_LINE_CLICK_RIGHT) r += glX;
              if((mActionLineMode & SquareClipGL2D.CLIP_LINE_CLICK_BOTTOM) == SquareClipGL2D.CLIP_LINE_CLICK_BOTTOM) b += glY;
              if((mActionLineMode & SquareClipGL2D.CLIP_LINE_CLICK_TOP) == SquareClipGL2D.CLIP_LINE_CLICK_TOP) t += glY;

              mClipGl.changeLineVertices(l, r, b, t);
              mInvalidate |= INVALIDATE_DO;
              return true;
          case MotionEvent.ACTION_UP:
          case MotionEvent.ACTION_POINTER_UP:
              mActionMode = ACTION_MODE_NONE;
              mClipGl.setDrawLineFlag(SquareClipGL2D.CLIP_LINE_CLICK_NONE);
              mInvalidate |= INVALIDATE_DO;
              return true;
        }
        return false;
    }

    private float spacing(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return FloatMath.sqrt(x * x + y * y);
     }

    private void savedActionData() {
        mActionZoomSavedPosX = mMainImage.getTranslateX();
        mActionZoomSavedPosY = mMainImage.getTranslateY();
        mActionZoomSavedScale = mScale;
    }

    private synchronized void zoom(float s) {
        float scale = mActionZoomSavedScale * s;

        if(scale < mMinScale) scale = mMinScale;
        else if(scale > mMaxScale) scale = mMaxScale;

        mScale = scale;

        float moveX = mActionZoomSavedPosX + (mActionZoomMidPos.x / scale - mActionZoomMidPos.x / mActionZoomSavedScale );
        float moveY = mActionZoomSavedPosY + ((mHeight - mActionZoomMidPos.y) / scale - (mHeight - mActionZoomMidPos.y) / mActionZoomSavedScale );

        mMainImage.scale(scale, scale);
        mClipGl.scale(scale, scale);
        move(moveX, moveY);

        mInvalidate |= INVALIDATE_DO;
    }

    private synchronized void move(float x , float y) {
        float scale = mScale;

        float moveX = x;
        float moveY = y;

        if(mWidth < mMainImage.getWidth() * scale) {
            if(moveX > 0) moveX = 0;
            else if(mWidth / scale > mMainImage.getWidth() + moveX) moveX = mWidth / scale - mMainImage.getWidth();
        } else {
            moveX = (mWidth / scale - mMainImage.getWidth()) / 2;
        }

        if(mHeight < mMainImage.getHeight() * scale) {
            if(moveY > 0) moveY = 0;
            else if(mHeight / scale > mMainImage.getHeight() + moveY) moveY = mHeight / scale - mMainImage.getHeight();
        } else {
            moveY = (mHeight / scale - mMainImage.getHeight()) / 2;
        }

        mMainImage.move(moveX, moveY);
        mClipGl.move(moveX, moveY);
        mInvalidate |= INVALIDATE_DO;
    }
}
